package com.snoob.springcloudalibaba.util.rsa;

import com.snoob.springcloudalibaba.base.BaseResult;
import lombok.Data;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @描述: TODO 接口入参工具类
 * @作者: lixing lixing_java@163.com
 * @日期 2020/4/4 13:25
 * <p>
 * 1、时间戳超时机制：【时间戳超时机制是防御DOS攻击的有效手段】
 * 用户每次请求都带上当前时间的时间戳，服务端接收到后跟当前时间进行比对，如果时间差大于一定时间则认为该请求失效
 * <p>
 * 2、签名机制：【签名机制保证了数据不会被篡改】
 * 加密后的数据就是本次请求的签名sign，服务端接收到请求后以同样的算法得到签名，并跟当前的签名进行比对，如果不一样，说明参数被更改过，直接返回错误标识
 * <p>
 * 3、拒绝重复调用
 * 客户端第一次访问时，将签名sign存放到缓存服务器中，【超时时间设定为跟时间戳的超时时间一致】，二者时间一致可以保证无论在timestamp限定时间内还是外URL都只能访问一次
 * 如果有人使用同一个URL再次访问，如果发现缓存服务器中已经存在了本次签名，则拒绝服务
 * 如果在缓存中的签名失效的情况下，有人使用同一个URL再次访问，则会被时间戳超时机制拦截
 */
@Data
public class InterfaceUtil {
    /** 调用方身份ID（接口发布方根据不同的成员提供） */
    private String appId;
    /** 密钥（接口发布方根据不同的成员提供） */
    private String secret;
    /** 调用接口的时间戳 */
    private long timestamp;
    /** 接口调用的签名值 */
    private String sign;

    /**
     * @描述 签名加密(MD5加密)
     * @作者 lixing
     * @日期 2020/4/4 15:17
     * @Param [appId, secret, timestamp]
     * @return java.lang.String
     */
    public static String md5Sign(String appId, String secret, long timestamp) {
        MessageDigest messageDigest = null; // 生成一个MD5加密计算摘要
        try {
            messageDigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5加密错误");
        }
        messageDigest.update((appId + secret + timestamp).getBytes());// 计算md5函数
        /**
         * digest()最后确定返回md5 hash值，返回值为8位字符串。
         * 因为md5 hash值是16位的hex值，实际上就是8位的字符
         * BigInteger函数则将8位的字符串转换成16位hex值，用字符串来表示；得到字符串形式的hash值
         * 一个byte是八位二进制，也就是2位十六进制字符（2的8次方等于16的2次方）
         */
        String hashedPwd = new BigInteger(1, messageDigest.digest()).toString(16); // 16是表示转换为16进制数
        return hashedPwd; // DigestUtils.md5DigestAsHex((appId + secret + timestamp).getBytes());
    }

    /**
     * @描述 TODO 签署签名
     * @作者 lixing
     * @日期 2020/4/4 14:56
     * @Param [appId, secret]
     * @return
     */
    public InterfaceUtil(String appId, String secret) {
        this.appId = appId;
        this.secret = secret;
        // 获得系统的时间，单位为毫秒 Long.parseLong("1585981429999");
        this.timestamp = System.currentTimeMillis();
        this.sign = md5Sign(appId, secret, timestamp);
    }

    /**
     * @描述 TODO 验证签名
     * @作者 lixing
     * @日期 2020/4/4 14:59
     * @Param [
     *  appId 系统分配（请求传入）,
     *  secret 系统分配（根据请求传入的appId从系统换取的）,
     *  timestamp 签名颁发时的时间戳（单位毫秒）（请求传入）,
     *  sign 签名（请求传入）,
     *  cycleTimestamp 签名有效周期（单位毫秒）（系统后台配置）
     * ]
     * @return java.lang.String
     */
    public static BaseResult<String> parseSign(String appId, String secret, long timestamp, String sign, long cycleTimestamp) {
        // 当前时间戳与签名颁发时的时间戳相差的毫秒数
        long timeDifferenceSeconds = System.currentTimeMillis() - timestamp;
        if (timeDifferenceSeconds <= 0) {
            // （当前时间戳）小于请求传入的（签名颁发时的时间戳）
            return new BaseResult<String>().fail("非法请求,时间戳非法");
        } else if (timeDifferenceSeconds >= cycleTimestamp) {
            // （当前时间戳）大于请求传入的（签名颁发时的时间戳），但是超过了接口安全机制指定的有效周期
            return new BaseResult<String>().fail("非法请求,时间戳超时");
        } else {
            // 根据请求参数生成签名
            String sign_ = md5Sign(appId, secret, timestamp);
            if (sign.equals(sign_)) {
                return new BaseResult<String>().success(sign_);
            } else {
                return new BaseResult<String>().fail("非法请求，验签失败");
            }
        }
    }

}
